#include <iostream>
#include <vector>
#include <conio.h>
#include <ctime>
#include <Windows.h>

using std::vector;

// Globe Class
class Body; //Body of snake
class Game; //Game
class Food; //Food
//------------

// Globe Variable
vector<Body> snake; //The snake
int map_l = 50, map_w = 25;//The map's long and width
//---------------

// Globe Function
void Gotoxy(SHORT x, SHORT y); //Set the pos to x,y
//---------------

void Gotoxy(SHORT x, SHORT y) {
	COORD pos; pos.X = x; pos.Y = y;
	HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(out, pos);
}

class Body {
public:
	Body(int value_x = 0,int value_y = 0) :x(value_x), y(value_y) {}
	int x, y;
};

class Food {
public:
	Food() :x(0), y(0), score(0) {}
	int x = 0, y = 0, score = 0;
};

class Game {
public:
	Game() : direction(4), score(0), EatFood(false) { food.score = 5; }
	void Init(int map_l, int map_w); //The Init the Game
	void Move(int direction);	//Move the snake
	void CreateFood(Food& food); //Create the food
	void Gaming(int level);	//Play the game
private:
	Food food; //The food
	int direction; //The 1,2,3,4 instant of up,down,left,right
	int score; //The score
	bool EatFood;
};
void Game::Init(int map_l, int map_w) {
	using std::cout;
	using std::endl;
	system("cls");
	//Load the snake
	for (int i = 4; i >= 2; --i) snake.push_back(Body(i, 2));
	//Paint the map
	for (SHORT i = 1; i <= map_l; ++i) {
		Gotoxy(i, 1); cout << '-';
	}
	for (SHORT i = 2; i <= map_w; ++i) {
		Gotoxy(1, i);
		cout << '|';
		Gotoxy(map_l, i);
		cout << '|';
	}
	for (SHORT i = 2; i <= map_l; ++i) {
		Gotoxy(i, map_w);
		cout << '-';
	}
	//Paint the snake
	for (auto p = snake.begin(); p != snake.end(); ++p) {
		Gotoxy(p->x,p->y);
		cout << "*";
	}
	//Paint the Lable
	Gotoxy(map_l + 4, 4);
	cout << "Length: " << 3; Gotoxy(map_l + 4, 6);
	cout << "Score: " << 0 << endl;
	//Paint the Food
	CreateFood(food);
	Gotoxy(food.x, food.y);
	cout << "@";
}
void Game::Move(int direction) {
	using std::cout;
	switch (direction) {
	case 1: //The up
		snake.insert(snake.begin(), Body(snake.begin()->x, snake.begin()->y - 1));
		break;
	case 2: //The down
		snake.insert(snake.begin(), Body(snake.begin()->x, snake.begin()->y + 1));
		break;
	case 3: //The left
		snake.insert(snake.begin(), Body(snake.begin()->x - 1, snake.begin()->y));
		break;
	case 4: //The right
		snake.insert(snake.begin(), Body(snake.begin()->x + 1, snake.begin()->y));
		break;
	default: throw "The Error of Direction";
	}
	//Move snake
	if (!EatFood) {
		Gotoxy(snake.rbegin()->x, snake.rbegin()->y);
		cout << ' '; //Delete the snake's end
		snake.pop_back();
	}
	else { EatFood = false; Gotoxy(map_l + 4, 4), cout << "Length: " << snake.size(); } //Debug
	Gotoxy(snake.begin()->x, snake.begin()->y); 
	cout << "*"; //Create the snake's head
}
void Game::CreateFood(Food& food) {
	srand((unsigned int)time(nullptr)); //Randomize
	while (1) {
        bool flag = false; //Food in Snake
		food.x = rand() % (map_l - 2) + 2;
		food.y = rand() % (map_w - 2) + 2;
		for (auto p = snake.begin(); p != snake.end(); ++p) {
            if (food.x == p->x and food.y == p->y) { flag = true; break; }
		}
        if (!flag) break;
	}
}
void Game::Gaming(int level) {
	using std::cout;
	using std::endl;
	int score = 0;	//The total score
	int length = 0;	//The snake's length
	Init(map_l, map_w);
	while (1) {;
		if (_kbhit()) { //When the keyboard has been hit
			int flag_hit = _getch(); //get the hit
			if ((flag_hit == 119 or flag_hit == 87) and direction != 2) direction = 1; // hit 'w' or 'W'
			else if ((flag_hit == 115 or flag_hit == 83) and direction != 1) direction = 2; // hit 's' or 'S'
			else if ((flag_hit == 97 or flag_hit == 65) and direction != 4) direction = 3; // hit 'a' or 'A'
			else if ((flag_hit == 100 or flag_hit == 68) and direction != 3) direction = 4; //hit 'd' or 'D'
			else if ((flag_hit == 27)) { //hit 'Esc'
                exit(0);
			}
			while (_kbhit()) _getch();
		}
		if (snake.begin()->x == food.x and snake.begin()->y == food.y) { //Snake eat the food
			score += food.score;
			EatFood = true;
			Gotoxy(map_l + 4, 4);
			cout << "Length: " << snake.size(); Gotoxy(map_l + 4, 6);
			cout << "Score: " << score << endl;
			CreateFood(food); Gotoxy(food.x, food.y); cout << "@";
		}
		Move(direction);
		if (snake.begin()->x == 1 or snake.begin()->x == map_l or
			snake.begin()->y == 1 or snake.begin()->y == map_w) { //Snake hit wall
			system("cls"); Gotoxy(10, 5);
			cout << "Game Over"; Gotoxy(10, 6);
			cout << "Length: " << snake.size(); Gotoxy(10, 7);
			cout << "Score: " << score << endl;
			return;
		}
		for (auto p = snake.begin() + 1; p != snake.end(); ++p) {
			if (snake.begin()->x == p->x and snake.begin()->y == p->y) { //Snake eat body
				system("cls"); Gotoxy(10, 5);
				cout << "Game Over"; Gotoxy(10, 6);
				cout << "Length: " << snake.size(); Gotoxy(10, 7);
				cout << "Score: " << score << endl;
				return;
			}
		}
		Sleep(500 - 50 * level);
	}
}

int main() {
	using std::cout;
	using std::endl;
	using std::cin;
	Game game;
	cout << "***********************************" << endl;
	cout << "*         Gluttonous Snake        *" << endl;
	cout << "***********************************" << endl;
	cout << "*     Press Any Keys to Continue  *" << endl;
	cout << "***********************************" << endl;
	_getch();
	cout << endl;
    cout << "     Select Difficulty(1 to 9)     ";
	int level = 0; cin >> level;
	if (level <= 0 or level >= 10)  cout << "Select Error!" << endl;
	else game.Gaming(level);
    cout << "Press Any Keys to Continue" << endl;
    _getch();
	return 0;
}